'use strict'

const express = require('express');
const http = require('http');
const url = require('url');
const WebSocket = require('ws');
const log4js = require('log4js');
const crypto = require('crypto');
var bodyParser = require('body-parser');

const config = require('./config');

log4js.configure(config.log4js_conf);
const logger = log4js.getLogger('notice');

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

const wsMap = {};
const KEY = config.key;

//初始化
for(let project of config.project){
  wsMap[project] = new Map();
}

const errMsg = {
  id_not_null: 'id不能为空',
  project_not_null: '系统名不能为空',
  project_is_error: '系统名错误',
  msg_not_null: '消息不能为空',
  socket_not_exist: '找不到对应的websocket链接',
  send_msg_error: '消息发送异常',
  success: '成功'
};

//获取一个异常header
const getResMsg = (key) =>{
  if (errMsg[key]){
    return {
      code: key,
      message: errMsg[key]
    }
  }else{
    return {
      code: 'unknow_error',
      message: '未定义异常：' + key
    }
  }
}

app.get('/status', (req, res) => {
  let json = {};
  let sum = 0;
  for(let k in wsMap){
    json[k] = wsMap[k].size;
    sum += json[k];
  }
  json._count = wss.clients.size;    //链接数
  json._valid = sum;          //有效链接数
  res.json(json);
});

app.post('/push', (req, res) => {
  console.log(req.body)
  let id = req.body.id;
  let project = req.body.project;
  let message = req.body.message;
  if (!id){
    res.json(getResMsg('id_not_null'));
    return;
  }
  if (!project){
    res.json(getResMsg('project_not_null'));
    return;
  }
  if (!message){
    res.json(getResMsg('msg_not_null'));
    return;
  }
  let map = wsMap[project];
  if (!map){
    res.json(getResMsg('project_is_error'));
    return;
  }
  logger.debug('push message to project=%s, id=%s', project, id);
  let ws = map.get(id);
  if (!ws){
    res.json(getResMsg('socket_not_exist'));
    return;
  }
  try{
    ws.send(message);
    res.json(getResMsg('success'));
  }catch(e){
    logger.error('send message error, project=%s, id=%s : ', project, id, e);
    let resMsg = getResMsg('send_msg_error');
    resMsg.stack = e;
    res.json(resMsg);
  }
});

const server = http.createServer(app);
config.ws.server = server
const wss = new WebSocket.Server(config.ws, () =>{
  logger.info('websocket listening on ' + config.ws.port);
});

wss.on('connection', (ws, req) => {
  const location = url.parse(req.url, true);
  const id = location.query.id;
  const project = location.query.project;
  const sign = location.query.sign;
  logger.debug('new connection id=%s, project=%s, sign=%s', id, project, sign);

  if (! checkSocket(id, project, sign)){
    logger.info('error connection.')
    ws.close(4000, 'url is error!');    //状态码 4000~4999 为私有使用且不能被注册
    return;
  }

  //暂不考虑 id 重复的情况
  // let old = wsMap.get(id);
  // if (old){
  //   logger.debug('close old connection id=%s', id);
  //   old.close();     //此处关闭是异步操作，代码继续执行，会先把新链接加入到map，然后触发close事件，会把新链接删除
  // }

  wsMap[project].set(id, ws);

  ws.on('close', (code, reason) => {
    logger.debug('client close connection id=%s', id);
    wsMap[project].delete(id);
  });

  ws.on('error', (e) => {
    logger.warn('connection error id=%s, error=', id, e);
    wsMap[project].delete(id);
  });
});

const hexMd5 = (str) => {
  let md5 = crypto.createHash('md5');
  md5.update(str, 'utf-8');
  return md5.digest('hex');
}

/**
 * 校验链接是否合法
 */
const checkSocket = (id, project, sign) => {
  if (config.project.indexOf(project) === -1){
    logger.debug('project is error');
    return false;
  }
  if (! config.check_sign){   //判断配置是否校验签名
    return true;
  }
  if (id && sign){
    let tarSign = hexMd5(id + '^' + project + '^' + KEY);
    logger.debug(`post sign is ${sign}, check sign is ${tarSign}`);
    return sign === tarSign;
  }
  return false;
};

server.listen(config.web.port, function listening() {
  logger.info('web Listening on %d', server.address().port);
});

